15장 더하고 빼는 맞춤 동작

출처: 원서 『API Design Patterns』(JJ Geewax, Manning).

이 장에서 딱 3가지만 1. 관계에 메타데이터가 필요 없을 때, 연관 리소스 대신 add/remove 동작으로 더 단순하게 다대다 관계를 다루는 법을 설명한다. 2. add/remove를 어느 쪽 리소스에 붙일지(관리 리소스 선택)를 구분한다. 3. 중복으로 넣거나 없는 걸 빼려 할 때 어떤 응답을 줘야 하는지를 설명한다.

이 장의 학습 목표는 세 가지입니다. - add/remove 동작이 연관 리소스보다 단순한 이유를 설명한다. - "그룹에 사용자를 넣는다"처럼 add/remove를 어느 리소스에 붙일지 구분한다. - 중복 추가·없는 것 제거 같은 무결성 상황의 응답 코드를 설명한다.


들어가기 전에 — 또 멤버십 카드를 만들어야 해?

지난 장(14장)에서 우리는 "사용자가 그룹에 속한다" 같은 다대다 관계를 다루려고 연관 리소스라는 걸 따로 만들었습니다.

그런데 막상 써 보니 이런 적 있죠?

그냥 "이 사람을 이 그룹에 넣고 싶을 뿐"인데, 멤버십이라는 리소스를 만들고, 그 멤버십을 또 Get·Update·Delete·List 하는 메서드를 줄줄이 만들고… 메서드가 일곱 개나 됐습니다.

역할도 가입일도 필요 없는데 말이죠.

그게 바로 이 장이 풀려는 문제입니다. 관계에 추가로 적을 정보가 없을 때는, 멤버십 카드 같은 걸 만들지 말고 그냥 "넣고(add)" "빼는(remove)" 동작 두 개만 주자는 거예요.

이렇게 관계의 존재 여부만 다루는 더하기·빼기 동작, 이게 add/remove 동작입니다. (0장 용어집 21번)


비유 먼저 — 멤버십 카드 vs 그냥 초대

비유 API/코드 위험·주의
동호회에 "회원 카드"를 발급하고 가입일·등급을 적어 둠 14장 연관 리소스 (Membership) 메서드가 7개로 늘어 무거움
카톡 단톡방에 사람을 그냥 "초대"하고 "내보내기" 15장 add/remove 동작 역할·가입일 같은 정보는 못 적음
명함첩에 이름만 꽂아 둠 (관계 종류는 안 적음) 관계의 존재 여부만 저장 "친구인지 동료인지"는 알 수 없음

핵심 한 줄: 적을 정보가 없으면, 카드를 만들지 말고 더하기·빼기만 하자.

14장: CreateMembership({ 사용자: "김철수", 그룹: "반모임", 역할: "관리자" })
      → 멤버십 리소스 생성. 이후 Get·Update·Delete·List까지 줄줄이.

15장: AddGroupUser({ 그룹: "groups/반모임", 사용자: "users/김철수" })
      → 끝! 추가 리소스 없음. add·remove·목록만 있으면 됨.

1. 언제 더하기·빼기로 충분할까 — 메타데이터가 없을 때

이런 적 있죠?

게시글에 태그를 달려고 하는데, 누가 "이 태그-게시글 연결의 가입일이 언제예요? 역할은요?"라고 물으면 황당하죠.

태그는 그냥 붙어 있거나(O) 안 붙어 있거나(X) 둘 중 하나입니다. 역할도 만료일도 없습니다. 그게 전부예요.

이렇게 관계에 딸려 적을 정보(메타데이터)가 없는 경우, 멤버십 카드 같은 연관 리소스는 과합니다.

메타데이터가 필요 없는 다대다 관계 예 5가지

1. 소셜 미디어 팔로우
   사용자 ↔ 사용자 (팔로우한다/안 한다, 역할 없음)
   → AddUserFollower / RemoveUserFollower

2. 게시글 태그
   게시글 ↔ 태그 (붙어 있다/없다)
   → AddPostTag / RemovePostTag

3. 상품 카테고리
   상품 ↔ 카테고리 (소속 여부만)
   → AddCategoryProduct / RemoveCategoryProduct

4. 재생목록 영상
   재생목록 ↔ 영상 (들어 있다/없다)
   → AddPlaylistVideo / RemovePlaylistVideo

5. 사용자 역할 (역할이 문자열이 아니라 별도 리소스일 때)
   사용자 ↔ 역할 (가지고 있다/없다)
   → AddUserRole / RemoveUserRole

한눈에 — 14장이 좋은 경우 vs 15장이 좋은 경우

비유 API/코드 위험·주의
회원증에 등급·가입일을 적어야 함 14장 연관 리소스 관계에 정보가 정말 있을 때만
단톡방 초대처럼 들었다/안 들었다만 15장 add/remove 나중에 정보가 필요해지면 갈아엎어야 함

한 걸음 더 ▸ 정보가 없어도 14장을 쓸 수 있다 메타데이터가 없어도 연관 리소스(14장)를 쓰는 게 틀린 건 아닙니다. 다만 메서드가 더 많아 무겁죠. "단순하게 가고 싶다"가 15장을 고르는 이유입니다.


2. 더하기·빼기를 어느 쪽에 붙일까 — 관리 리소스 고르기

이런 적 있죠?

"그룹에 사용자를 추가"인지 "사용자에 그룹을 추가"인지, 둘 다 말이 되는 것 같아서 어디에 메서드를 달지 한참 고민한 적 있죠?

여기서 새 용어 하나가 나옵니다.

관리 리소스란, 관계를 관리하는 쪽 리소스입니다. 쉽게 말해 add/remove 버튼이 달리는 쪽이에요. (단톡방에서 방장 역할을 맡는 그 방 자체라고 보면 됩니다.) 그리고 거기에 더해지고 빠지는 쪽을 연관 대상 리소스라고 부릅니다. (방에 초대되는 손님 쪽이죠.)

둘 중 하나만 고른다

방식 1: 그룹이 관리 리소스 (그룹이 사용자를 관리)
─────────────────────────────────────────────
POST /groups/1/users:add     { userId: "users/alice" }   ← "그룹에 사용자를 추가"
POST /groups/1/users:remove  { userId: "users/alice" }   ← "그룹에서 사용자를 제거"

방식 2: 사용자가 관리 리소스 (사용자가 그룹을 관리)
─────────────────────────────────────────────
POST /users/alice/groups:add     { groupId: "groups/1" }  ← "사용자에 그룹을 추가"
POST /users/alice/groups:remove  { groupId: "groups/1" }  ← "사용자에서 그룹을 제거"

방식 1과 방식 2를 둘 다 주면 안 됩니다. 같은 일을 두 군데서 할 수 있어 헷갈리거든요. 딱 한쪽만 고릅니다.

고르는 요령 — "A에 B를 넣는다"가 자연스러운 쪽이 A

질문은 딱 하나입니다. "누가 관계를 관리하는 게 자연스러운가?"

관계                  관리 리소스   왜
────────────────────────────────────────────────
그룹 ↔ 사용자         그룹          "그룹에 사람을 초대"
재생목록 ↔ 영상       재생목록      "재생목록에 영상 추가"
게시글 ↔ 태그         게시글        "게시글에 태그 달기"
사용자 ↔ 팔로워       사용자(대상)  "이 사용자를 팔로우"
상품 ↔ 카테고리       카테고리      "카테고리에 상품 배치"
장바구니 ↔ 상품       장바구니      "장바구니에 상품 담기"

비유로 연결

비유 API/코드 위험·주의
단톡방 방장만 초대/강퇴 가능 그룹이 관리 리소스 멤버 본인은 스스로 못 들어옴
재생목록 주인이 영상을 끼워 넣음 재생목록이 관리 리소스 영상 쪽엔 add 버튼이 없음
양쪽 다 초대 버튼을 달면? 방식 1+2 동시 제공 같은 일이 두 군데, 혼란

핵심 한 줄: "A에 B를 추가한다"가 입에 더 붙는 쪽이 A(관리 리소스)다.

잠깐 — 팔로우는 사실 양쪽 다 말이 된다 (정답 1개가 아님)

위 표에서 팔로우는 "사용자(대상)"이 관리 리소스라고 적어 뒀습니다. 그런데 이걸 외워야 할 정답으로 받아들이면 곤란해요. 팔로우는 양쪽 다 말이 되거든요.

먼저 이런 적 있죠? 인스타에서 누군가를 팔로우할 때, "내가 그 사람을 내 팔로잉 목록에 넣는 것"처럼 느껴지기도 하고, 동시에 "그 사람의 팔로워 목록에 내가 들어가는 것"처럼 느껴지기도 하죠. 둘 다 자연스럽습니다. 그게 정상이에요.

비유 API/코드 위험·주의
"그 사람의 팔로워 명단에 나를 올린다" 받는 사용자가 관리 리소스 → AddUserFollower 한쪽만 외우면 다른 설계를 틀렸다고 오해
"내 팔로잉 명단에 그 사람을 올린다" 하는 사용자가 관리 리소스 → AddUserFollowing 도메인에 따라 이쪽이 더 자연스러울 수도

그래서 어느 쪽이 맞느냐고요? 둘 다 맞습니다. 어느 쪽으로 둘지는 우리 서비스의 도메인과 팀 합의로 정하는 거예요.

잘못된 사고 (기계적 규칙으로 외움)
"팔로우 = 받는 사용자가 관리 리소스. 끝. 외우자."
→ AddUserFollowing 설계를 보고 "이건 틀렸다"고 단정 → 틀린 판단

올바른 사고 (양쪽 검토 후 합의)
"팔로우는 받는 쪽·하는 쪽 둘 다 관리 리소스가 될 수 있다.
 우리 서비스에선 '내 팔로잉 관리'가 주 화면이니 하는 쪽으로 가자."
→ 팀과 합의해 한쪽을 고름 → 근거 있는 판단

한 걸음 더 ▸ '자연스러운 쪽'은 사람마다 다를 수 있다 §2의 "A에 B를 넣는 게 자연스러운 쪽" 요령은 대부분 잘 맞습니다. 다만 팔로우처럼 양쪽이 팽팽하면 요령만으로 결론이 안 납니다. 그럴 땐 "외운 답"을 꺼내지 말고, 양쪽을 적어 보고 팀과 합의하는 게 정답입니다.

핵심 한 줄: 팔로우는 받는 쪽·하는 쪽 둘 다 관리 리소스가 될 수 있다 — 정답은 팀 합의다.


3. 어떻게 생겼나 — add/remove 메서드 만들기

사용자 메서드 모양 그대로

add/remove는 새로운 발명이 아닙니다. 0장 용어집 8번의 사용자 메서드(표준 틀 밖의 맞춤 동작) 모양을 그대로 빌려 옵니다. 즉 POST + 콜론(:) + 동작 이름이에요.

표 — Add / Remove 메서드 요약

액션                  메서드                      HTTP
─────────────────────────────────────────────────────────────────
Alice를 그룹 1에 추가  AddGroupUser({              POST /groups/1/users:add
                        parent: "groups/1",        Body: { userId: "users/1" }
                        userId: "users/1" })       → 200 OK (반환값 없음)

Alice를 그룹 1서 제거  RemoveGroupUser({           POST /groups/1/users:remove
                        parent: "groups/1",        Body: { userId: "users/1" }
                        userId: "users/1" })       → 200 OK (반환값 없음)

이름 짓는 규칙

규칙은 단순합니다. Add/Remove + 관리리소스(단수) + 대상리소스(단수)

  • AddGroupUser (그룹에 사용자 추가)
  • RemoveGroupUser (그룹에서 사용자 제거)
  • AddPlaylistVideo (재생목록에 영상 추가)
  • RemovePostTag (게시글에서 태그 제거)

잘못된 예 vs 올바른 예 — 본문에 뭘 담을까

요청 본문에는 추가할 대상의 식별자만 담습니다. 전체 정보를 다 담으면 안 됩니다.

잘못된 예: 전체 리소스를 다 담음
POST /groups/1/users:add
{
    user: {
        id: "users/alice",
        name: "Alice",            ← 불필요
        email: "alice@test.com"   ← 이걸 보내면 User도 수정되는 건가? 혼란!
    }
}

올바른 예: 식별자만 담음
POST /groups/1/users:add
{
    userId: "users/alice"          ← 추가할 대상 식별자만!
}

왜 식별자만 담을까요? 나머지 정보는 "관계를 맺는다"는 본래 목적과 상관이 없고, 그걸 받으면 클라이언트가 "어, 이걸 보내면 사용자 정보도 같이 고쳐지나?" 하고 오해할 수 있기 때문입니다.

비유로 연결

비유 API/코드 위험·주의
단톡방 초대에 전화번호(ID)만 입력 userId만 본문에 담기 이름·이메일까지 보내면 오해
초대했다고 그 사람 프로필이 바뀌진 않음 add는 관계만 만든다 본문에 프로필 담으면 수정처럼 보임

4. 양쪽으로 보기 — 그룹의 사용자, 사용자의 그룹

목록은 양방향으로

관계를 만들었으면 양쪽에서 다 볼 수 있어야겠죠. "이 그룹에 누가 있나?"도, "이 사용자는 어느 그룹에 있나?"도 둘 다요.

표 — list 메서드 요약

질문                   메서드                       HTTP
─────────────────────────────────────────────────────────────────
그룹 1에 속한 사용자는?  ListGroupUsers({            GET /groups/1/users
                          parent: "groups/1" })      → User[] 반환

사용자 1이 속한 그룹은?  ListUserGroups({            GET /users/1/groups
                          parent: "users/1" })       → Group[] 반환

여기서 중요한 점 하나. 돌아오는 건 User[] / Group[] (진짜 리소스)입니다. 멤버십 같은 중간 카드가 아니에요. 그런 카드는 애초에 만들지 않았으니까요.

새 용어 — 암시적 서브컬렉션

GET /groups/1/users 이 주소를 보면 마치 users가 그룹의 서브리소스(0장 용어집 15번)인 것처럼 보입니다. 그런데 사실은 아닙니다.

암시적 서브컬렉션이란, 진짜 서브리소스는 아닌데 주소만 서브컬렉션처럼 생긴 구조를 말합니다. (방 안에 진짜로 들어 있는 게 아니라, "이 방과 관련된 사람들"을 보여 주는 안내판 같은 거예요.)

GET /groups/1/users
→ users가 그룹의 진짜 서브리소스처럼 보이지만,
  실제로는 "그룹 1과 연관된 사용자 목록"을 보여 주는 편의 주소다.

진짜 서브리소스:      /groups/1/settings   (그룹이 소유)
암시적 서브컬렉션:    /groups/1/users      (그룹과 연관된 사용자)

사람이 많으면 — 페이징

그룹에 사용자가 수천 명일 수 있으니, 한 번에 다 주지 않고 끊어서 줍니다.

GET /groups/1/users?maxPageSize=50
→ {
    users: [ ...최대 50명... ],
    nextPageToken: "eyJ0..."        ← 다음 페이지 열쇠
}

다음 페이지
GET /groups/1/users?maxPageSize=50&pageToken=eyJ0...
→ {
    users: [ ... ],
    nextPageToken: null             ← 마지막 페이지
}

비유로 연결

비유 API/코드 위험·주의
단톡방 멤버 목록 / 내가 든 단톡방 목록 양방향 list 메서드 한쪽만 만들면 반대편 조회 불가
방 안내판("관련 인물")은 방 내부물이 아님 암시적 서브컬렉션 진짜 서브리소스로 오해 금지
멤버 5천 명을 한 화면에 다 못 띄움 페이징(nextPageToken) 페이징 빼면 거대 응답으로 터짐

그룹 본문에 멤버를 통째로 박지 마라 — 개수만, 목록은 따로

이런 적 있죠? 그룹 하나를 GetGroup으로 불러왔는데, 그 안에 멤버 5천 명이 배열로 통째로 딸려 와서 응답이 수 메가바이트가 된 적.

그룹 본문(Group 리소스)에 멤버 목록을 배열로 끼워 넣는(인라인) 건 함정입니다. 관계가 많아지면 그 그룹을 조회할 때마다 멤버 전체가 따라와 응답이 터지거든요.

비유 API/코드 위험·주의
동호회 소개판에 회원 전원 이름을 다 박음 Group 본문에 users 배열 인라인 회원 5천이면 소개판 한 장에 안 들어감
소개판엔 "회원 32명"만, 명단은 게시판에서 따로 Group엔 userCount만, 목록은 list 메서드 본문 가볍고, 명단은 페이징으로 끊어서

그래서 규칙은 이렇습니다. 본문엔 개수(userCount)만 두고, 실제 목록은 §4 앞에서 본 list 메서드로 따로 받자.

잘못된 예: 그룹 본문에 멤버 목록을 배열로 인라인
GET /groups/1
→ {
    id: "groups/1",
    name: "엔지니어링",
    users: [                       ← 멤버 전부를 본문에 박음
      { id: "users/alice", ... },
      { id: "users/bob", ... },
      ...5천 명...                  ← 그룹 한 번 부를 때마다 5천 명이 따라옴!
    ]
}

올바른 예: 개수만 본문에, 목록은 별도 메서드
GET /groups/1
→ {
    id: "groups/1",
    name: "엔지니어링",
    userCount: 5000                ← 개수만! 본문이 가볍다
}

실제 명단이 필요하면 따로 (페이징으로 끊어서)
GET /groups/1/users?maxPageSize=50      ← ListGroupUsers
→ { users: [ ...최대 50명... ], nextPageToken: "..." }
# 잘못된 예 — 본문에 목록을 통째로 담는 응답
def get_group_bad(group_id):
    group = db.get_group(group_id)
    group["users"] = db.all_members(group_id)  # 5천 명을 그대로 붙임
    return group  # 그룹 조회마다 거대 응답

# 올바른 예 — 개수만 담고, 목록은 list 메서드에 맡김
def get_group_good(group_id):
    group = db.get_group(group_id)
    group["userCount"] = db.count_members(group_id)  # 숫자 하나뿐
    return group  # 가벼움. 명단은 ListGroupUsers로 따로 받음

핵심 한 줄: 관계가 많으면 본문엔 개수(userCount)만, 명단은 list 메서드로.


5. 실수했을 때 — 중복 추가와 없는 것 제거

이런 적 있죠?

이미 단톡방에 있는 사람을 또 초대 버튼 눌렀더니 그냥 조용히 넘어가거나 에러가 나거나… 서버마다 제멋대로면 클라이언트가 헷갈리죠.

그래서 표준 규칙을 정해 둡니다.

기본 규칙 (안전판) — 상황별 응답 코드

상황 1: 정상 추가
POST /groups/1/users:add { userId: "users/jimmy" }
→ 200 OK   (Jimmy가 그룹 1에 추가됨)

상황 2: 중복 추가 (이미 멤버)
POST /groups/1/users:add { userId: "users/jimmy" }   (두 번째)
→ 409 Conflict   ("이미 멤버입니다")

상황 3: 정상 제거
POST /groups/1/users:remove { userId: "users/jimmy" }
→ 200 OK   (Jimmy가 그룹 1에서 제거됨)

상황 4: 없는 사람 제거
POST /groups/1/users:remove { userId: "users/unknown" }
→ 412 Precondition Failed   ("이 사용자는 이 그룹의 멤버가 아닙니다")

상황 5: 존재하지 않는 사용자를 추가
POST /groups/1/users:add { userId: "users/nonexistent" }
→ 404 Not Found   ("해당 사용자가 존재하지 않습니다")

외울 건 딱 세 개입니다. - 중복 추가 → 409 Conflict - 없는 것 제거 → 412 Precondition Failed - 아예 없는 대상 → 404 Not Found

409가 떠도 사실 목적은 이뤄졌다

여기서 0장 용어집 10번의 멱등성이 빛납니다. 같은 add를 두 번 보내서 두 번째에 409가 떠도, 결과적으로 Jimmy는 그룹의 멤버가 맞습니다.

add 1번 → 200 OK    (Jimmy 멤버됨)
add 2번 → 409 충돌   (이미 멤버)
어느 쪽이든 결과: Jimmy는 그룹 1의 멤버. 목적 달성!

그래서 "관계를 만들고 싶다"가 목적이라면, 클라이언트는 409도 성공으로 취급해도 됩니다.

14장과 비교 — 안은 같고 겉만 단순

14장 (연관 리소스):
CreateMembership({ userId: "jimmy", groupId: "1" })  → 201 Created
CreateMembership({ userId: "jimmy", groupId: "1" })  → 409 Conflict
→ 멤버십이 명시적 리소스라 중복 검증도 눈에 보임

15장 (add/remove):
AddGroupUser({ parent: "groups/1", userId: "users/jimmy" })  → 200 OK
AddGroupUser({ parent: "groups/1", userId: "users/jimmy" })  → 409 Conflict
→ 안에서는 관계를 저장하지만 리소스로 드러내지 않음
→ 검증 로직은 같고, 클라이언트가 보기엔 더 단순

이렇게 중복을 막는 것을 0장 용어집 26번의 고유성 제약이라고 합니다. (같은 짝이 두 번 생기지 않게 막는 규칙이죠.)

비유로 연결

비유 API/코드 위험·주의
이미 든 사람을 또 초대 409 Conflict 조용히 무시하면 클라이언트가 헷갈림
없는 사람을 내보내기 412 Precondition Failed 200으로 속이면 진짜 처리된 줄 오해
두 번 눌러도 결국 멤버는 멤버 멱등성 409를 무조건 실패로 보면 재시도 지옥

한 걸음 더 ▸ 빈칸 채우기 아래 빈칸에 응답 코드를 채워 보세요. - 이미 있는 태그를 또 add → ___ Conflict - 안 달린 태그를 remove → ___ Precondition Failed - 존재하지도 않는 태그 ID를 add → ___ Not Found (정답: 409 / 412 / 404)


6. 단순함의 대가 — 무엇을 포기했나

add/remove는 단순해서 좋지만, 공짜는 아닙니다. 두 가지를 내려놓아야 해요.

6-1. 한쪽으로만 가능 — 비상호관계

새 용어가 또 하나 나옵니다.

비상호관계란, 관리 리소스를 한쪽에만 둬서 양방향이 대칭이 아닌 상태를 말합니다. (단톡방에서 방장만 초대할 수 있고, 손님은 스스로 못 들어오는 그 비대칭이에요.)

그룹이 관리 리소스일 때:
가능:  POST /groups/1/users:add        ← "그룹에 사용자를 추가" (자연스러움)
불가:  POST /users/alice/groups:add    ← 이런 메서드는 없음!

→ "Alice를 engineering 그룹에 넣고 싶다"면
   POST /groups/engineering/users:add 를 호출해야 한다.
   POST /users/alice/groups:add 는 못 쓴다.

6-2. 관계에 정보를 못 적음

14장 (연관 리소스): 관계에 정보 저장 가능
{
    userId: "alice",
    groupId: "engineering",
    role: "admin",            ← 역할
    joinedAt: "2024-03-15",   ← 가입일
    expireTime: "2025-03-15"  ← 만료일
}

15장 (add/remove): 존재 여부만
"Alice는 engineering의 멤버이다"     (O)
"Alice는 engineering의 admin이다"    (X)  ← 못 적음!
"Alice가 2024-03-15에 가입했다"      (X)  ← 못 적음!

이게 0장 용어집 20번의 관계 메타데이터입니다. add/remove는 이걸 담을 자리가 없어요.

정보를 꼭 적어야 한다면? 14장의 연관 리소스로 갈아타거나, 관계와 별도로 그 정보를 저장할 다른 방법을 찾아야 합니다.

비유로 연결

비유 API/코드 위험·주의
방장만 초대 가능(손님은 못 들어옴) 비상호관계 반대편에서 호출하려다 막힘
명함첩에 이름만, 관계 종류는 못 적음 관계 메타데이터 불가 나중에 역할 필요해지면 갈아엎기

7. 그래서 14장이냐 15장이냐 — 고르는 순서도

마지막으로 둘 중 무엇을 고를지 정리합니다.

Q1: 관계에 정보(역할·가입일 등)가 필요한가?
├── 예  → 14장 연관 리소스 (멤버십 패턴)
│
└── 아니오 → Q2: API를 최대한 단순하게 하고 싶은가?
            ├── 예  → 15장 add/remove
            └── 아니오 → 14장 연관 리소스 (정보 없이도 쓸 수 있음)

Q3: 나중에 정보가 필요해질 가능성이 큰가?
├── 예  → 처음부터 14장 (나중에 갈아엎는 비용 방지)
└── 아니오 → 15장으로 시작, 필요해지면 14장으로 전환

한눈에 비교표 (14장 vs 15장)

항목 14장 연관 리소스 15장 add/remove
중간 관계 리소스 멤버십(별도) 없음(숨김)
관계 관련 메서드 수 최대 7개 4개
관계에 정보 적기 가능 불가
복잡도 높음 낮음
양쪽으로 조회 별칭 메서드 list 메서드
양방향 대칭 대칭 비대칭(한쪽만 관리)
목록 반환 타입 멤버십[] User[] / Group[]
중복 생성 409 Conflict 409 Conflict
적합한 때 정보가 필요할 때 존재 여부만 필요할 때

메서드 개수로 느끼는 단순함

User 관련:   5개 (Create·Get·List·Update·Delete)
Group 관련:  5개 (Create·Get·List·Update·Delete)
관계 관련:   4개 (Add·Remove·ListGroupUsers·ListUserGroups)
────────────────────────────────────────────
총 14개 — 14장(17개)보다 3개 적다!

8. 스스로 풀어 보기 — 워크드 예제에서 독립 적용까지

워크드 예제 (다 보여 줌)

문제: Recipe와 Ingredient의 다대다 관계에서 관리 리소스는 어느 쪽일까?

풀이: "레시피에 재료를 추가한다"가 자연스럽습니다. 그러니 Recipe가 관리 리소스예요. - AddRecipeIngredient / RemoveRecipeIngredient - ListRecipeIngredients (이 레시피에 필요한 재료) - ListIngredientRecipes (이 재료를 쓰는 레시피)

부분 완성 (빈칸 채우기)

문제: 팔로우 관계의 메서드 이름을 채워 보세요. "이 사용자에 팔로워를 추가" → Add______Follower "이 사용자에서 팔로워를 제거" → Remove______Follower

힌트: 관리 리소스는 팔로우를 받는 사용자입니다. (정답: AddUserFollower / RemoveUserFollower)

독립 적용 (혼자서)

문제: "Alice가 engineering 그룹에서 admin 역할을 갖는다"를 15장 패턴으로 표현할 수 있나요?

생각해 볼 점: 15장은 관계의 존재 여부만 저장합니다. admin은 역할, 즉 관계 메타데이터죠. (답: 표현할 수 없습니다. 역할은 메타데이터라서 14장 연관 리소스의 role 필드가 필요합니다.)


한 줄 정리

  • 관계에 적을 정보가 없으면, 멤버십 카드(14장) 대신 더하기·빼기(15장)로 단순하게.
  • add/remove는 한쪽(관리 리소스)에만 붙인다 — "A에 B를 넣는다"가 자연스러운 쪽이 A.
  • 본문엔 식별자만, 목록은 양방향(User[]/Group[]), 주소는 암시적 서브컬렉션.
  • 중복 추가 409, 없는 것 제거 412, 없는 대상 404. 멱등성 덕에 409도 사실 목적 달성.
  • 대가는 비상호관계(한쪽만 가능)와 메타데이터 못 적음. 정보가 필요하면 14장으로.

다음 장 예고: 16장에서는 한 자리에 여러 타입이 올 수 있는 다형성을 다룹니다. 지금 몰라도 됩니다.

난이도
에피소드
질문
카드를 로딩 중...
답변

클릭하거나 Space를 눌러 뒤집기

0 / 0
학습 진도 0%
이동   Space 뒤집기   R 셔플